/*
 * Copyright (c) 2007 The Trustees of Indiana University. All rights reserved.
 * IU CSCI B534 Distributed Systems 2007-02-01 http://www.cs.indiana.edu/classes/b534
 */

package edu.indiana.cs.b534.torrent.impl;

import edu.indiana.cs.b534.torrent.Constants;
import edu.indiana.cs.b534.torrent.TorrentException;
import edu.indiana.cs.b534.torrent.Utils;

import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Logger;

/**
 * Details about a piece (index in the torrent, hash of its value,
 * and the actual byte value for this piece
 */
public class PieceInfo {

    private static Logger logger = Logger.getLogger(TorrentManager.TORRENT_MANAGER_NAME);

    // index of this piece
    private int index;

    // hash of the piece
    private String hash;

    // whether this particular piece is completed downloading or not
    private boolean isCompleted = false;

    // the real content of this piece
    private byte[] value;

    // this will hold information about the availability of blocks. The index designates the index
    // of the block within this piece and the content will be boolean availability
    private boolean[] blockAvailabilityInformation;

    private int downloadedBlockCount = 0;

    private final int pieceLength;

    public PieceInfo(int index, String hash, int pieceLength) {
        this(index, new byte[pieceLength], false, hash);

    }

    public PieceInfo(int index, byte[] value, boolean completed, String hash) {
        this.index = index;
        this.value = value;
        isCompleted = completed;
        this.hash = hash;
        int blockCount = (int) Math.ceil(((double) value.length) / Constants.BLOCK_SIZE);
        blockAvailabilityInformation = new boolean[blockCount];
        this.pieceLength = value.length;
        logger.info("Piece " + index + " has " + blockCount + " blocks");
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public int getIndex() {
        return index;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public String getHash() {
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof PieceInfo && ((PieceInfo) obj).getIndex() == index) {
            return true;
        }
        return false;
    }


    public synchronized boolean isCompleted() {

        return isCompleted;
    }

    public synchronized void setCompleted(boolean completed) throws TorrentException {

        // we need to do anything here, only either when the piece is not completed or piece is completed
        // and we wanna mark it not completed explicitly
        if (completed) {

            boolean calculatedCompletedStatus = true;
            for (int i = 0; i < blockAvailabilityInformation.length; i++) {
                boolean b = blockAvailabilityInformation[i];
                if (!b) {
                    calculatedCompletedStatus = false;
                    break;
                }
            }

            if (!calculatedCompletedStatus) {
                throw new RuntimeException("isCompleted status stored within the piece is different from the calculated status." + calculatedCompletedStatus);
            }

            // ok, before marking this as completed, we need to make sure the hash is correct for this piece
            byte[] hashOfDownloadedPiece;
            try {
                hashOfDownloadedPiece = Utils.computeHash(value);

                byte[] hashOfBytes = new byte[20];
                char[] chars = hash.toCharArray();
                for (int i = 0; i < chars.length; i++) {
                    hashOfBytes[i] = (byte) chars[i];
                }


                if (ByteBuffer.wrap(hashOfDownloadedPiece).compareTo(ByteBuffer.wrap(hashOfBytes)) != 0) {
                    throw new TorrentException("Hash check failed for the piece " + index);
                }
            } catch (NoSuchAlgorithmException e) {
                throw new TorrentException(e);
            }
        }

        isCompleted = completed;
    }

    public synchronized byte[] getValue() {
        return value;
    }

    public synchronized void setValue(byte[] value) throws TorrentException {
        this.value = value;

        for (int i = 0; i < blockAvailabilityInformation.length; i++) {
            blockAvailabilityInformation[i] = true;
        }

        setCompleted(true);
    }


    public synchronized boolean isBlockAvailabile(int blockIndex) {
        return blockAvailabilityInformation[blockIndex];
    }

    public synchronized void setBlockAvailability(Boolean isBlockAvailable, int blockIndex) throws TorrentException {
        // if this method gets duplicates calls for the same block, then we are screwed :((. But we
        // believe in our code ;)

//        logger.info("Setting block availability as " + isBlockAvailable + " for the block number " + blockIndex);
        blockAvailabilityInformation[blockIndex] = isBlockAvailable;

        // if the downloaded block count is equal to the number of piece to be downloaded, then we
        // are done with downloading this piece
        boolean isAllBlocksAvailable = true;
        for (int i = 0; i < blockAvailabilityInformation.length; i++) {
            if(!blockAvailabilityInformation[i]){
                isAllBlocksAvailable = false;
            }
        }
        if(isAllBlocksAvailable){
            setCompleted(true);
        }


    }

    public int getPieceLength() {
        return pieceLength;
    }

}
